// Copyright 2024 Vadim Kharitonovich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package log

import (
	"context"
	"fmt"
	"net"
	"time"
)

type Level uint8

const (
	DebugLevel Level = iota
	InfoLevel
	WarnLevel
	ErrorLevel
	FatalLevel
)

type (
	Opt func(o Options)
)

func Context(ctx context.Context) Opt {
	return func(o Options) {
		o.Context(ctx)
	}
}

func Format(args ...any) Opt {
	return func(o Options) {
		o.Format(args...)
	}
}

func Str(key, val string) Opt {
	return func(o Options) {
		o.Str(key, val)
	}
}

func Strs(key string, vals []string) Opt {
	return func(o Options) {
		o.Strs(key, vals)
	}
}

func Stringer(key string, val fmt.Stringer) Opt {
	return func(o Options) {
		o.Stringer(key, val)
	}
}

func Stringers(key string, vals []fmt.Stringer) Opt {
	return func(o Options) {
		o.Stringers(key, vals)
	}
}

func Bytes(key string, val []byte) Opt {
	return func(o Options) {
		o.Bytes(key, val)
	}
}

func Hex(key string, val []byte) Opt {
	return func(o Options) {
		o.Hex(key, val)
	}
}

func RawJSON(key string, b []byte) Opt {
	return func(o Options) {
		o.RawJSON(key, b)
	}
}

func RawCBOR(key string, b []byte) Opt {
	return func(o Options) {
		o.RawCBOR(key, b)
	}
}

func AnErr(key string, err error) Opt {
	return func(o Options) {
		o.AnErr(key, err)
	}
}

func Errs(key string, errs []error) Opt {
	return func(o Options) {
		o.Errs(key, errs)
	}
}

func Err(err error) Opt {
	return func(o Options) {
		o.Err(err)
	}
}

func Stack() Opt {
	return func(o Options) {
		o.Stack()
	}
}

func Bool(key string, b bool) Opt {
	return func(o Options) {
		o.Bool(key, b)
	}
}

func Bools(key string, b []bool) Opt {
	return func(o Options) {
		o.Bools(key, b)
	}
}

func Int(key string, i int) Opt {
	return func(o Options) {
		o.Int(key, i)
	}
}

func Ints(key string, i []int) Opt {
	return func(o Options) {
		o.Ints(key, i)
	}
}

func Int8(key string, i int8) Opt {
	return func(o Options) {
		o.Int8(key, i)
	}
}

func Ints8(key string, i []int8) Opt {
	return func(o Options) {
		o.Ints8(key, i)
	}
}

func Int16(key string, i int16) Opt {
	return func(o Options) {
		o.Int16(key, i)
	}
}

func Ints16(key string, i []int16) Opt {
	return func(o Options) {
		o.Ints16(key, i)
	}
}

func Int32(key string, i int32) Opt {
	return func(o Options) {
		o.Int32(key, i)
	}
}

func Ints32(key string, i []int32) Opt {
	return func(o Options) {
		o.Ints32(key, i)
	}
}

func Int64(key string, i int64) Opt {
	return func(o Options) {
		o.Int64(key, i)
	}
}

func Ints64(key string, i []int64) Opt {
	return func(o Options) {
		o.Ints64(key, i)
	}
}

func Uint(key string, i uint) Opt {
	return func(o Options) {
		o.Uint(key, i)
	}
}

func Uints(key string, i []uint) Opt {
	return func(o Options) {
		o.Uints(key, i)
	}
}

func Uint8(key string, i uint8) Opt {
	return func(o Options) {
		o.Uint8(key, i)
	}
}

func Uints8(key string, i []uint8) Opt {
	return func(o Options) {
		o.Uints8(key, i)
	}
}

func Uint16(key string, i uint16) Opt {
	return func(o Options) {
		o.Uint16(key, i)
	}
}

func Uints16(key string, i []uint16) Opt {
	return func(o Options) {
		o.Uints16(key, i)
	}
}

func Uint32(key string, i uint32) Opt {
	return func(o Options) {
		o.Uint32(key, i)
	}
}

func Uints32(key string, i []uint32) Opt {
	return func(o Options) {
		o.Uints32(key, i)
	}
}

func Uint64(key string, i uint64) Opt {
	return func(o Options) {
		o.Uint64(key, i)
	}
}

func Uints64(key string, i []uint64) Opt {
	return func(o Options) {
		o.Uints64(key, i)
	}
}

func Float32(key string, f float32) Opt {
	return func(o Options) {
		o.Float32(key, f)
	}
}

func Floats32(key string, f []float32) Opt {
	return func(o Options) {
		o.Floats32(key, f)
	}
}

func Float64(key string, f float64) Opt {
	return func(o Options) {
		o.Float64(key, f)
	}
}

func Floats64(key string, f []float64) Opt {
	return func(o Options) {
		o.Floats64(key, f)
	}
}

func Timestamp() Opt {
	return func(o Options) {
		o.Timestamp()
	}
}

func Times(key string, t []time.Time) Opt {
	return func(o Options) {
		o.Times(key, t)
	}
}

func Dur(key string, d time.Duration) Opt {
	return func(o Options) {
		o.Dur(key, d)
	}
}

func Durs(key string, d []time.Duration) Opt {
	return func(o Options) {
		o.Durs(key, d)
	}
}

func TimeDiff(key string, t time.Time, start time.Time) Opt {
	return func(o Options) {
		o.TimeDiff(key, t, start)
	}
}

func Any(key string, i interface{}) Opt {
	return func(o Options) {
		o.Any(key, i)
	}
}

func Interface(key string, i interface{}) Opt {
	return func(o Options) {
		o.Interface(key, i)
	}
}

func Type(key string, val interface{}) Opt {
	return func(o Options) {
		o.Type(key, val)
	}
}

func IPAddr(key string, ip net.IP) Opt {
	return func(o Options) {
		o.IPAddr(key, ip)
	}
}

func IPPrefix(key string, pfx net.IPNet) Opt {
	return func(o Options) {
		o.IPPrefix(key, pfx)
	}
}

func MACAddr(key string, ha net.HardwareAddr) Opt {
	return func(o Options) {
		o.MACAddr(key, ha)
	}
}

// Options configures single `Log` function call with additional options.
type Options interface {
	Context(ctx context.Context)
	// Format provides an arguments for message formatting.
	// If `Format` option passed then log message treated as a formattable string for`fmt.Sprintf`
	Format(args ...any)

	Str(key, val string)
	Strs(key string, vals []string)
	Stringer(key string, val fmt.Stringer)
	Stringers(key string, vals []fmt.Stringer)
	Bytes(key string, val []byte)
	Hex(key string, val []byte)
	RawJSON(key string, b []byte)
	RawCBOR(key string, b []byte)
	AnErr(key string, err error)
	Errs(key string, errs []error)
	Err(err error)
	Stack()
	Bool(key string, b bool)
	Bools(key string, b []bool)
	Int(key string, i int)
	Ints(key string, i []int)
	Int8(key string, i int8)
	Ints8(key string, i []int8)
	Int16(key string, i int16)
	Ints16(key string, i []int16)
	Int32(key string, i int32)
	Ints32(key string, i []int32)
	Int64(key string, i int64)
	Ints64(key string, i []int64)
	Uint(key string, i uint)
	Uints(key string, i []uint)
	Uint8(key string, i uint8)
	Uints8(key string, i []uint8)
	Uint16(key string, i uint16)
	Uints16(key string, i []uint16)
	Uint32(key string, i uint32)
	Uints32(key string, i []uint32)
	Uint64(key string, i uint64)
	Uints64(key string, i []uint64)
	Float32(key string, f float32)
	Floats32(key string, f []float32)
	Float64(key string, f float64)
	Floats64(key string, f []float64)
	Timestamp()
	Times(key string, t []time.Time)
	Dur(key string, d time.Duration)
	Durs(key string, d []time.Duration)
	TimeDiff(key string, t time.Time, start time.Time)
	Any(key string, i interface{})
	Interface(key string, i interface{})
	Type(key string, val interface{})
	IPAddr(key string, ip net.IP)
	IPPrefix(key string, pfx net.IPNet)
	MACAddr(key string, ha net.HardwareAddr)
}

type Logger interface {
	Debug(string, ...Opt)
	Error(string, ...Opt)
	Fatal(string, ...Opt)
	Info(string, ...Opt)
	Warn(string, ...Opt)

	Func(level Level, f func())
}
